package pl.edu.agh.mgr.hotel.ws.configuration;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.apache.commons.collections.list.TreeList;

import pl.edu.agh.mgr.hotel.ws.client.Query;
import pl.edu.agh.mgr.hotel.ws.client.ServiceDescription;
import pl.edu.agh.mgr.hotel.ws.client.utility.IWSClient;

public class PeersCache {

	private int cacheSize = 100;

	private LinkedList<ServiceDescription> peers = new LinkedList<ServiceDescription>();

	// the more the better
	private Map<ServiceDescription, Long> answersStat = new TreeMap<ServiceDescription, Long>();

	// hops to destination
	private Map<ServiceDescription, Long> hopsStat = new TreeMap<>();

	// mean time
	private Map<ServiceDescription, List<Long>> timeStat = new TreeMap<>();

	// neighbours degree
	private Map<ServiceDescription, Long> degreeStat = new TreeMap<>();

	public PeersCache(TreeSet<ServiceDescription> peers) {
		addPeers(peers);
		prepareStatistics();
	}

	public PeersCache(ServiceDescription peer) {
		addPeer(peer);
		prepareStatistics();
	}

	public void prepareStatistics() {
		for (ServiceDescription peer : peers) {
			answersStat.put(peer, 0L);
			hopsStat.put(peer, 0L);			
			degreeStat.put(peer, 0L);
		}
	}

	public TreeSet<ServiceDescription> getPeers() {
		return new TreeSet<ServiceDescription>(peers);
	}

	// based on statistics
	public LinkedHashSet<ServiceDescription> getPeers(NextPeerAlg nextPeerAlg) {

		if (nextPeerAlg.equals(NextPeerAlg.ANSWERS)) {
			return getSortedStatistics(answersStat);
		} else if (nextPeerAlg.equals(NextPeerAlg.DEGREE)) {
			return getSortedStatistics(degreeStat);
		} else if (nextPeerAlg.equals(NextPeerAlg.HOPS)) {
			return getSortedStatistics(hopsStat);
		} else if (nextPeerAlg.equals(NextPeerAlg.TIME)) {
			Map<ServiceDescription, Long> meanTimeStats = new HashMap<>();

			for (Map.Entry<ServiceDescription, List<Long>> entryItem : timeStat
					.entrySet()) {
				BigDecimal meanValue = new BigDecimal("0");

				for (Long timeItem : entryItem.getValue())
					meanValue.add(new BigDecimal(timeItem.toString()));

				if (!entryItem.getValue().isEmpty())
					meanTimeStats.put(entryItem.getKey(), Long
							.getLong(meanValue
									.divide(new BigDecimal(entryItem.getValue()
											.size())).toString()));
			}

			return getSortedStatistics(meanTimeStats);
		}

		return new LinkedHashSet<ServiceDescription>(peers);
	}

	private LinkedHashSet<ServiceDescription> getSortedStatistics(
			Map<ServiceDescription, Long> stats) {
		LinkedHashSet<ServiceDescription> sortedPeersStat = new LinkedHashSet<ServiceDescription>();
		List<Map.Entry<ServiceDescription, Long>> sortedEntryList = new ArrayList<>();

		sortedEntryList.addAll(stats.entrySet());
		Collections.sort(sortedEntryList,
				new Comparator<Map.Entry<ServiceDescription, Long>>() {

					@Override
					public int compare(Entry<ServiceDescription, Long> o1,
							Entry<ServiceDescription, Long> o2) {
						return o1.getValue().compareTo(o2.getValue());
					}

				});

		Collections.reverse(sortedEntryList);
		for (Map.Entry<ServiceDescription, Long> item : sortedEntryList)
			sortedPeersStat.add(item.getKey());

		return sortedPeersStat;
	}

	// based on pree search in serviceDescriptions
	public LinkedHashSet<ServiceDescription> getPeersBasedOnExtraData(List<Query> queries) {
		LinkedHashSet<ServiceDescription> extraDataPeersMatch = new LinkedHashSet<ServiceDescription>();

		for (ServiceDescription peer : peers) {
			if (compareServiceDescription(queries, peer))
				extraDataPeersMatch.add(peer);
		}
		
		return extraDataPeersMatch;
	}

	public boolean compareServiceDescription(List<Query> queries,
			ServiceDescription serviceDescription) {

		for (Query query : queries) {
			if (query.getAttribute().toLowerCase().equals("name")
					&& query.getValue().toLowerCase()
							.equals(serviceDescription.getName().toLowerCase())) {
				return true;
			}
			if (query.getAttribute().toLowerCase().equals("city")
					&& query.getValue()
							.toLowerCase()
							.equals(serviceDescription.getCity().getName()
									.toLowerCase())) {
				return true;
			}
			if (query.getAttribute().toLowerCase().equals("country")
					&& query.getValue()
							.toLowerCase()
							.equals(serviceDescription.getCountry().getName()
									.toLowerCase())) {
				return true;
			}
			if (query.getAttribute().toLowerCase().equals("province")
					&& query.getValue()
							.toLowerCase()
							.equals(serviceDescription.getProvince().getName()
									.toLowerCase())) {
				return true;
			}
			if (query.getAttribute().toLowerCase().equals("street")
					&& query.getValue()
							.toLowerCase()
							.equals(serviceDescription.getStreet().getName()
									.toLowerCase())) {
				return true;
			}
			if (query.getAttribute().toLowerCase().equals("hoteltype")
					&& query.getValue()
							.toLowerCase()
							.equals(serviceDescription.getHotelType().getName()
									.toLowerCase())) {
				return true;
			}
		}

		return false;
	}

	public void setPeers(Set<ServiceDescription> peers) {
		this.peers = new LinkedList<ServiceDescription>(peers);
	}

	public LinkedHashSet<pl.edu.agh.mgr.hotel.entities.ServiceDescription> getServerPeers() {
		LinkedHashSet<pl.edu.agh.mgr.hotel.entities.ServiceDescription> serverPeers = new LinkedHashSet<pl.edu.agh.mgr.hotel.entities.ServiceDescription>();
		for (ServiceDescription sd : peers) {
			serverPeers
					.add(new pl.edu.agh.mgr.hotel.entities.ServiceDescription(
							sd.getName(),
							new pl.edu.agh.mgr.hotel.entities.Country(sd
									.getCountry() != null ? sd.getCountry()
									.getName() : null, 3),
							new pl.edu.agh.mgr.hotel.entities.Province(sd
									.getProvince() != null ? sd.getProvince()
									.getName() : null, 3),
							new pl.edu.agh.mgr.hotel.entities.City(
									sd.getCity() != null ? sd.getCity()
											.getName() : null, 3),
							new pl.edu.agh.mgr.hotel.entities.Street(sd
									.getStreet() != null ? sd.getStreet()
									.getName() : null, 3), sd.getAddress(),
							new pl.edu.agh.mgr.hotel.entities.HotelType(sd
									.getHotelType() != null ? sd.getHotelType()
									.getName() : null, 3), null // FIXME :
																// rodzaje
							// pokoi dodac
							, sd.getWsAddress(), sd.getWsName(), sd
									.getDataLevel(), null));
		}

		return serverPeers;
	}

	public void addPeers(Set<ServiceDescription> peers) {
		for (ServiceDescription peer : peers)
			addPeer(peer);
	}

	public void addPeer(ServiceDescription peer) {
		if (peers.size() == cacheSize) {
			peers.removeLast();
		}
		peers.addFirst(peer);
	}

	public List<IWSClient> getClientPeers() {
		List<IWSClient> clientPeers = new ArrayList<>();
		for (ServiceDescription peer : peers) {
			clientPeers.add(WSUtil.getWSClient(peer.getName()));
		}

		return clientPeers;
	}

	// statistics

	public Map<ServiceDescription, Long> getAnswersStat() {
		return answersStat;
	}

	public void setAnswersStat(Map<ServiceDescription, Long> answersStat) {
		this.answersStat = answersStat;
	}

	public Map<ServiceDescription, Long> getDegreeStat() {
		return degreeStat;
	}

	public void setDegreeStat(Map<ServiceDescription, Long> degreeStat) {
		this.degreeStat = degreeStat;
	}

	public Map<ServiceDescription, Long> getHopsStat() {
		return hopsStat;
	}

	public void setHopsStat(Map<ServiceDescription, Long> hopsStat) {
		this.hopsStat = hopsStat;
	}

	public Map<ServiceDescription, List<Long>> getTimeStat() {
		return timeStat;
	}

	public void setTimeStat(Map<ServiceDescription, List<Long>> timeStat) {
		this.timeStat = timeStat;
	}

	public void updateAnswersStat(ServiceDescription peer) {
		if (answersStat.containsKey(peer)) {
			answersStat.put(peer, answersStat.get(peer) + 1);
		} else
			answersStat.put(peer, 1L);
	}

	public void updateDegreeStat(ServiceDescription peer, long neighbours) {
//		if (!degreeStat.containsKey(peer))
			degreeStat.put(peer, neighbours);
	}

	public void updateHopsStat(ServiceDescription peer, long hops) {
//		if (!hopsStat.containsKey(peer))
			hopsStat.put(peer, hops);
	}

	public void updateTimeStat(ServiceDescription peer, long time) {
		if (timeStat.containsKey(peer)) {
			timeStat.get(peer).add(time);
		} else {
			timeStat.put(peer, new ArrayList<Long>());
			timeStat.get(peer).add(time);
		}
	}

	public void printStatistics(Map<ServiceDescription, Long> peers) {
		for (ServiceDescription peer : peers.keySet()) {
			System.out.println(peer.getWsAddress() + " = " + peers.get(peer));
		}
	}

	public void printTimeStatistics(Map<ServiceDescription, List<Long>> peers) {
		for (ServiceDescription peer : peers.keySet()) {
			System.out.println(peer.getWsAddress() + " = " + peers.get(peer));
		}
	}
}
